﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.CodeDom.Compiler;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.CodeDom;
using EnvDTE;
using SymbolEditor.Addin.Helper;

namespace SymbolEditor.Addin.CodeDom
{
    class CodeDOMSerializer : CodeDomProvider, SymbolEditor.Addin.CodeDom.ICodeDOMSerializer
    {
        #region fields

        private CodeDomProvider provider = null;
        private string objDir = "obj"; 

        #endregion

        #region constuctor

        public CodeDOMSerializer(string language)
            : base()
        {
            provider = CodeDomProvider.CreateProvider(language);
        } 

        #endregion

        #region IDisposable 成员

        public void Dispose()
        {
            base.Dispose();
            provider.Dispose();
            provider = null;
        }

        #endregion

        #region public methods

        [Obsolete()]
        public override ICodeCompiler CreateCompiler()
        {
            return provider.CreateCompiler();
        }

        [Obsolete()]
        public override ICodeGenerator CreateGenerator()
        {
            return provider.CreateGenerator();
        }

        public override void GenerateCodeFromCompileUnit(System.CodeDom.CodeCompileUnit compileUnit, System.IO.TextWriter writer, CodeGeneratorOptions options)
        {
            if (writer is StreamWriter && ((writer as StreamWriter).BaseStream is FileStream))
            {
                string filename = ((writer as StreamWriter).BaseStream as FileStream).Name;
                string parentDir = (new FileInfo(filename)).Directory.FullName;
                string objDirectory = parentDir + "\\" + objDir;
                if (!Directory.Exists(objDirectory))
                {
                    Directory.CreateDirectory(objDirectory);
                }
                string domfilename = System.IO.Path.GetFileNameWithoutExtension(filename) + ".DOM";
                string domfullname = objDirectory + "\\" + domfilename;
                serializeCodeDOMUnit(compileUnit, domfullname);
                provider.GenerateCodeFromCompileUnit(compileUnit, writer, options);
            }
            else
            {
                throw new NotSupportedException("non file streamwriter is not supported!");
            }

        }

        public void GenerateCodeFromCompileUnit(System.CodeDom.CodeCompileUnit compileUnit, ProjectItem codeItem, CodeGeneratorOptions options)
        {
            if (codeItem.Kind == Constants.vsProjectItemKindPhysicalFile)
            {
                using (AddinHelper.SafeAccessDocument safeADoc = new AddinHelper.SafeAccessDocument(codeItem))
                {
                    //AddinHelper.SafeAccessDocument(codeItem);
                    //check whether the file being in read only status
                    if (codeItem.Document.ReadOnly)
                    {
                        throw new FileLoadException("The document '" + codeItem.Document.FullName + "' is read only!");
                    }
                    /*
                     * get the dom file coresponding to the code document,
                     *if the file is not exsit ,auto create it .
                     */
                    ProjectItem domDoc = getSameNameSubItem(codeItem);

                    using (AddinHelper.SafeAccessDocument safeADom = new AddinHelper.SafeAccessDocument(domDoc))
                    {
                        //AddinHelper.SafeAccessDocument(domDoc);

                        //get textwriter to the document file
                        string dompath = domDoc.Document.FullName;
                        using (StreamWriter writer = new StreamWriter(codeItem.Document.FullName))
                        {
                            serializeCodeDOMUnit(compileUnit, dompath);
                            provider.GenerateCodeFromCompileUnit(compileUnit, writer, options);
                        }
                    }
                }
            }
            else
            {
                throw new InvalidDataException("Invalid project item");
            }
        }

        public override System.CodeDom.CodeCompileUnit Parse(TextReader codeStream)
        {
            if (codeStream is StreamReader && ((codeStream as StreamReader).BaseStream is FileStream))
            {
                string filename = ((codeStream as StreamReader).BaseStream as FileStream).Name;
                string parentDir = (new FileInfo(filename)).Directory.FullName;
                string objDirectory = parentDir + "\\" + objDir;

                string domfilename = System.IO.Path.GetFileNameWithoutExtension(filename) + ".DOM";
                string domfullname = objDirectory + "\\" + domfilename;
                return DeserializeCodeDOMUnit(filename);
            }
            else
            {
                throw new NotSupportedException("non file streamReader is not supported!");
            }
        }

        public System.CodeDom.CodeCompileUnit Parse(ProjectItem codeItem)
        {
            CodeCompileUnit ret = null;
            if (codeItem.Kind == Constants.vsProjectItemKindPhysicalFile)
            {
                ProjectItem domDoc = getSameNameSubItem(codeItem);
                using (AddinHelper.SafeAccessDocument safeADoc = new AddinHelper.SafeAccessDocument(domDoc))
                {
                    ret = DeserializeCodeDOMUnit(domDoc.Document.FullName);
                }
            }
            return ret;
        }

        public override void GenerateCodeFromStatement(System.CodeDom.CodeStatement statement, System.IO.TextWriter writer, CodeGeneratorOptions options)
        {
            provider.GenerateCodeFromStatement(statement, writer, options);
        }  

        #endregion

        #region private methods

        private ProjectItem getSameNameSubItem(ProjectItem parentItem)
        {
            string filename = parentItem.Name + ".DOM";
            string filepath = parentItem.Document.Path + "\\" + filename;
            ProjectItem targetItem = null;
            IEnumerable<ProjectItem> targets = parentItem.ProjectItems.Cast<ProjectItem>().
                                                        Where(element => element.Name.ToUpper().Equals(filename.ToUpper()));
            if (targets.Count() > 0)
            {
                targetItem = targets.First();
            }
            else
            {
                if (File.Exists(filepath))
                    File.Delete(filepath);
                //if not exsit ,create it
                targetItem = AddinHelper.AddEmptyDoc2Project(parentItem.ProjectItems, filename);
            }
            return targetItem;
        }

        private void serializeCodeDOMUnit(System.CodeDom.CodeCompileUnit compileUnit, string domfullname)
        {
            IFormatter formatter = new BinaryFormatter();
            Stream stream = new FileStream(domfullname, FileMode.Create,
            FileAccess.Write, FileShare.None);
            try
            {
                formatter.Serialize(stream, compileUnit);

            }
            finally
            {
                stream.Close();
            }
        }

        private System.CodeDom.CodeCompileUnit DeserializeCodeDOMUnit(string domfullname)
        {
            if (File.Exists(domfullname))
            {
                CodeCompileUnit ret = null;
                IFormatter formatter = new BinaryFormatter();

                Stream stream = new FileStream(domfullname, FileMode.Open,
                FileAccess.Read, FileShare.None);
                try
                {
                    ret = formatter.Deserialize(stream) as CodeCompileUnit;
                }
                finally
                {
                    stream.Dispose();
                }
                return ret;
            }
            else
            {
                throw new FileNotFoundException(domfullname);
            }
        }

        #endregion
    }
     
}
